﻿using Microsoft.AspNetCore.Mvc;

using Serilog;

using System.Collections.Concurrent;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace GitHubCopilotProxies.Handlers;

public class CopilotCompletions
{
    static ConcurrentDictionary<string, HttpContext> RequestContext = new ConcurrentDictionary<string, HttpContext>();
    public static async Task CodeCompletions(HttpContext httpContext, [FromServices] IHttpClientFactory httpClientFactory)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await httpContext.Response.CompleteAsync();
            return;
        }

        var token = httpContext.Request.Headers.Authorization.ToString();
        RequestContext[token] = httpContext;

        // 增加防抖时间随机化（250-750ms 区间）
        var debounceTime = Random.Shared.Next(250, 750);
        try { await Task.Delay(debounceTime, httpContext.RequestAborted); }
        catch
        {
            if (RequestContext.TryGetValue(token, out var ctx) && ctx == httpContext)
                RequestContext.Remove(token, out _);
            return;
        }

        httpContext.Response.Headers["azureml-model-deployment"] = $"d{Random.Shared.Next(1, 100).ToString().PadLeft(3, '0')}-20250129112839";
        httpContext.Response.Headers["Content-Security-Policy"] = "default-src 'none'; sandbox";
        httpContext.Response.Headers["x-github-backend"] = "Kubernetes";
        httpContext.Response.Headers["x-request-id"] = httpContext.Request.Headers["x-request-id"];
        httpContext.Response.Headers["X-GitHub-Request-Id"] = "C0E1:6A1A:1A1F:2A1D:1A1F:1A1F:1A1F:1A1F";

        async Task WriteDone(int? statusCode = null)
        {
            httpContext.Response.ContentType = "text/event-stream";
            if (statusCode.HasValue)
                httpContext.Response.StatusCode = statusCode.Value;
            await httpContext.Response.WriteAsync("data: [DONE]\n\n");
        }
        if (RequestContext.TryGetValue(token, out var currContext) == false || currContext != httpContext)
        {
            await WriteDone();
            return;
        }

        try
        {
            var apiUrl = "https://api.siliconflow.cn/v1/completions";
            var apiKey = Environment.GetEnvironmentVariable("SILICONFLOW_API_KEY");

            var bodyJson = await httpContext.Request.ReadFromJsonAsync<JsonElement>(httpContext.RequestAborted);
            var updatedBody = JsonObject.Create(bodyJson)!;
            updatedBody["model"] = "Pro/Qwen/Qwen2.5-Coder-7B-Instruct";
            updatedBody.Remove("extra");
            updatedBody.Remove("nwo");

            if (updatedBody.TryGetPropertyValue("max_tokens", out var maxToken) == false)
                updatedBody["max_tokens"] = 256;

            // VS2022 Codex 使用语言模型模式带全部参照文本
            var withSnippet = false;
            // 代码编辑走免费版本
            if (httpContext.Request.Path.Value?.StartsWith("/v1/engines/copilot-centralus-h100/") is true)
            {
                updatedBody["model"] = "Qwen/Qwen2.5-Coder-7B-Instruct";
                updatedBody["max_tokens"] = 4096;
            }
            else if (httpContext.Request.Headers.TryGetValue("editor-version", out var editorVersion) && editorVersion.ToString().StartsWith("VisualStudio"))
            {
                var snippet = string.Empty;
                var prompt = updatedBody["prompt"]!.ToString();
                #region 处理 prompt
                var snippetBuilder = new StringBuilder();
                var promptBuilder = new StringBuilder();
                var preMark = true;
                using (var reader = new StringReader(prompt))
                {
                    string? line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (preMark && line.StartsWith("//"))
                            snippetBuilder.AppendLine(line);
                        else if (preMark)
                        {
                            preMark = false;
                            promptBuilder.AppendLine(line);
                        }
                        else promptBuilder.AppendLine(line);
                    }
                }
                snippet = snippetBuilder.ToString();
                prompt = promptBuilder.ToString();
                #endregion

                // VS2022 的代码补全请求转到超长上下文对话模式
                const string aliblUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
                var aliblKey = Environment.GetEnvironmentVariable("ALIBAILIAN_API_KEY");
                var codexType = Environment.GetEnvironmentVariable("VS2022CODEX");
                withSnippet = true;
                apiUrl = aliblUrl;
                apiKey = aliblKey;
                updatedBody["model"] = "qwen-turbo";
                // 使用语言模型模式带全部参照文本
                // 重构 VS2022 的请求体，使用带提示词的对话模式
                var newBody = updatedBody.DeepClone().AsObject();
                var language = bodyJson.GetProperty("extra").GetProperty("language").GetString();
                newBody.Remove("prompt");
                newBody.Remove("suffix");
                var systemPrompt = $$"""
                    You are an AI coding assistant named GitHub Copilot assisting a user working in Visual Studio.
                    Next, you will receive three pieces of code. The first one is the description of the relevant reference code, the second one is the first part of the code, and the third one is the last part of the code.
                    You need to complete the code based on the context, you only need to answer the code part, no other content needs to be answered.
                   
                    Strictly follow these principles:
                    1. Generate the minimal code delta that seamlessly integrates with the provided suffix. Ensure that the code you generate adds the least amount of new content necessary to connect smoothly with the subsequent code.
                    2. Conduct thorough syntax continuity checks before outputting the code. This means verifying that the generated code adheres to the syntax rules of {{language}} and flows logically with the existing code.
                    3. Avoid repeating any existing code patterns. Your output should introduce new and relevant code that contributes to the completion of the task without duplicating what has already been provided.
                    4. Prioritize single-line implementations. Aim for single-line solutions 90% of the time, as they are often the most concise and efficient way to complete the code.
                    
                    Important guidelines:
                    - Only generate code content. Do not include any non-code explanations or text.
                    - Do not provide the entire completed code. The maximum number of code lines in your response should not exceed 3.
                    - Your answer must not be similar to the content of the reference code suffix. Ensure that the generated code does not cross the boundary of the suffix, as this will result in duplicate code.
                    - Complete the code in the most concise way possible. If a single-line solution is feasible, return only 1 line of code.
                    - You must guess the intention based on the context, if unsure of the intention, you must output empty directly and end the conversation.
                    """;
                newBody["messages"] = new JsonArray() {
                    new JsonObject() {
                        { "role", "system" },
                        { "content", systemPrompt },
                    },
                    new JsonObject() {
                        { "role", "user" },
                        { "content", $"## Related code snippets.\n{snippet}" }
                    },
                    new JsonObject() {
                        { "role", "user" },
                        { "content", $"## The preceding text of the code.\n{prompt}" }
                    },
                    new JsonObject() {
                        { "role", "user" },
                        { "content", $"## The following text of the code.\n{updatedBody["suffix"]}" }
                    },
                };
                newBody["max_tokens"] = 48;
                newBody["temperature"] = 0.1;
                newBody["top_p"] = 0.9;
                updatedBody = newBody;
            }
            //else updatedBody["temperature"] = 1;

            if (updatedBody.TryGetPropertyValue("n", out var nNode) && nNode!.GetValue<int>() > 1)
                updatedBody["n"] = 1;

            if (string.IsNullOrEmpty(apiKey)) { await WriteDone(StatusCodes.Status500InternalServerError); return; }

            // 发起请求前再次检查最新的请求是否当前会话
            if (RequestContext.TryGetValue(token, out var currentContext) == false || currentContext != httpContext)
            {
                await WriteDone();
                return;
            }
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
            cts.Token.Register(() => httpContext.Abort()); // 超过 10s 不开始返回的中断请求，这种补全基本没用
            using var httpClient = httpClientFactory.CreateClient();
            httpClient.Timeout = TimeSpan.FromSeconds(10);
            using var httpRequest = new HttpRequestMessage(HttpMethod.Post, apiUrl);
            httpRequest.Content = new StringContent(updatedBody.ToJsonString(AppJsonSerializerContext.Web.Options), Encoding.UTF8, "application/json");
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
            using var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, httpContext.RequestAborted);
            response.EnsureSuccessStatusCode();
            httpContext.Response.StatusCode = (int)response.StatusCode;
            httpContext.Response.ContentType = response.Content.Headers.ContentType!.MediaType;
            using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
            using var outputStream = httpContext.Response.Body;
            if (withSnippet)
            {
                using var reader = new StreamReader(responseStream);
                string? line;
                while ((line = await reader.ReadLineAsync(httpContext.RequestAborted)) != null)
                {
                    const string blockContent = """{"content":"```"}""";
                    const string emptyContent = """{"content":""}""";
                    if (line.Contains("""{"content":"```"}"""))
                        line = line.Replace(blockContent, emptyContent);
                    await outputStream.WriteAsync(Encoding.UTF8.GetBytes(line + "\n"), httpContext.RequestAborted);
                }
            }
            else await responseStream.CopyToAsync(outputStream, 128, httpContext.RequestAborted);
            await httpContext.Response.CompleteAsync();
        }
        catch (OperationCanceledException) { return; }
        catch (Exception ex) { Log.Error(ex, "补全接口发生异常"); }
        finally { RequestContext.Remove(token, out _); }
    }
    public static async Task ChatCompletions(HttpContext httpContext, [FromServices] IHttpClientFactory httpClientFactory)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        JsonElement bodyJson;
        try { bodyJson = await httpContext.Request.ReadFromJsonAsync<JsonElement>(httpContext.RequestAborted); } catch { return; }
        var requestBody = JsonObject.Create(bodyJson)!;
        var sourceModelName = requestBody["model"]!.GetValue<string>();
        requestBody["model"] = "deepseek-chat";

        #region 确认要调用的API接口和模型
        string apiUrl = string.Empty, apiKey = string.Empty, modelName = string.Empty;
        const string siliconflowApiUrl = "https://api.siliconflow.cn/v1/chat/completions";
        const string deepseekApiUrl = "https://api.deepseek.com/v1/chat/completions";
        const string alibailianApiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
        const string volcengineApiUrl = "https://ark.cn-beijing.volces.com/api/v3/chat/completions";
        string siliconflowApiKey = Environment.GetEnvironmentVariable("SILICONFLOW_API_KEY") ?? string.Empty;
        string deepseekApiKey = Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? string.Empty;
        string alibailianApiKey = Environment.GetEnvironmentVariable("ALIBAILIAN_API_KEY") ?? string.Empty;
        string volcengineApiKey = Environment.GetEnvironmentVariable("VOLCENGINE_API_KEY") ?? string.Empty;

        const string VS2022AcaTip = "You are an AI coding assistant named GitHub Copilot assisting a user working in Visual Studio.";
        const string VS2022EpmTip = "You work on a team and are an expert project manager with a strong background in software development.";
        const string VS2022SoqTip = "Write a short one-sentence question that I can ask that naturally follows from the previous few questions and answers.";
        const string VSCodeCatTip = "If you cannot choose just one category, or if none of the categories seem like they would provide the user with a better result, you must always respond with \"unknown\".";
        const string VSCodeSasTip = "Suggest a succinct (under 6 words) follow-up message to the last AI-assistant response.";
        const string VSCodeWbtTip = "Please write a brief title for the chat conversation above.";
        if (requestBody["messages"]!.AsArray().Any(item =>
        {
            var content = item!.AsObject()["content"]!.ToString();
            return content.StartsWith(VS2022AcaTip) || content.StartsWith(VS2022EpmTip) || content.StartsWith(VS2022SoqTip) ||
                   content.Contains(VSCodeCatTip) || content.StartsWith(VSCodeSasTip) || content.StartsWith(VSCodeWbtTip);
        }))
        {
            modelName = "Qwen/Qwen2.5-Coder-7B-Instruct"; apiUrl = siliconflowApiUrl; apiKey = siliconflowApiKey;
            if (httpContext.Request is { ContentLength: > 16000 }) // 长上下文超过阈值的转到 qwen-turbo 处理
            { modelName = "qwen-turbo"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; }
        }

        if (!string.IsNullOrEmpty(apiKey)) { }
        else if (sourceModelName.StartsWith("o3-mini")) { modelName = "deepseek-v3-241226"; apiUrl = volcengineApiUrl; apiKey = volcengineApiKey; }
        else if (sourceModelName.StartsWith("o1-mini")) { modelName = "deepseek-r1-250120"; apiUrl = volcengineApiUrl; apiKey = volcengineApiKey; }
        else if (sourceModelName.StartsWith("o1-preview")) { modelName = "qwen-max"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; }
        else if (sourceModelName.StartsWith("o1")) { modelName = "qwen-plus"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; }
        else if (sourceModelName.StartsWith("gpt-4o")) { modelName = "deepseek-chat"; apiUrl = deepseekApiUrl; apiKey = deepseekApiKey; }
        else switch (sourceModelName)
            {
                case "claude-3.5-sonnet": modelName = "deepseek-v3"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; break;
                case "claude-3.7-sonnet": modelName = "deepseek-r1"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; break;
                case "claude-3.7-sonnet-thought": modelName = "qwq-32b"; apiUrl = alibailianApiUrl; apiKey = alibailianApiKey; break;
                case "gemini-2.0-flash-001": modelName = "Qwen/Qwen2.5-Coder-32B-Instruct"; apiUrl = siliconflowApiUrl; apiKey = siliconflowApiKey; break;
            }
        if (string.IsNullOrEmpty(apiUrl) || string.IsNullOrEmpty(apiKey))
        {
            httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
            return;
        }
        requestBody["model"] = modelName;
        #endregion

        #region 删除工具调用的字段信息
        // 处理消息内容
        if (!requestBody.TryGetPropertyValue("function_call", out _))
        {
            var messages = requestBody["messages"]!.AsArray();
            for (int i = 0; i < messages.Count; i++)
            {
                var message = messages[i]!.AsObject();
                if (!message.TryGetPropertyValue("tool_calls", out _))
                    message.Remove("tool_calls");
            }
            var lastIndex = messages.Count - 1;
            const string chatLocale = "zh-cn";
            var lastMessage = messages[lastIndex]!.AsObject();
            var content = lastMessage["content"]!.AsValue().ToString();
            if (!content.Contains("Respond in the following locale"))
                lastMessage["content"] = content + $"\r\nRespond in the following locale: {chatLocale}.";
        }
        requestBody.Remove("intent");
        requestBody.Remove("intent_threshold");
        requestBody.Remove("intent_content");
        requestBody.Remove("tools");
        requestBody.Remove("tool_call");
        requestBody.Remove("functions");
        requestBody.Remove("function_call");
        requestBody.Remove("tool_choice");
        #endregion
        #region 更改提示词，移除专用AI限制
        foreach (var (index, item) in requestBody["messages"]!.AsArray().Index())
        {
            if (index > 0) break;
            var message = item!.AsObject()!;
            var content = message["content"]!.ToString();
            const string copilotVsCode = "you must respond with \"GitHub Copilot\".";
            if (content.Contains(copilotVsCode)) content = content.Replace(copilotVsCode, $"you can respond with \"GitHub Copilot\" and use the {modelName} model.");
            const string policyVsCode = "Follow Microsoft content policies.\n";
            if (content.Contains(policyVsCode)) content = content.Replace(policyVsCode, string.Empty);
            const string avoidVsCode = "Avoid content that violates copyrights.\n";
            if (content.Contains(avoidVsCode)) content = content.Replace(avoidVsCode, string.Empty);
            var matchModel = Regex.Match(content, @"You use the (.*?) model\.");
            if (matchModel.Success) content = content.Replace(matchModel.Value, $"You use the {modelName} model.");
            // 取消无关开发话题过滤提示
            const string relatedTip = "For questions not related to software development, simply give a reminder that you are an AI programming assistant.\r\n";
            if (content.Contains(relatedTip)) content = content.Replace(relatedTip, string.Empty);
            message["content"] = content;
        }
        #endregion

        // 处理最大 token 数
        int chatMaxTokens = 4096;
        if (requestBody.TryGetPropertyValue("max_tokens", out var maxToken) == false || maxToken!.GetValue<int>() > chatMaxTokens)
            requestBody["max_tokens"] = chatMaxTokens;
        // 处理 n 参数
        if (requestBody.TryGetPropertyValue("n", out var nNode) && nNode!.GetValue<int>() > 1)
            requestBody["n"] = 1;

        try
        {
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300));
            cts.Token.Register(() => httpContext.Abort());
            using var httpClient = httpClientFactory.CreateClient();
            httpClient.Timeout = TimeSpan.FromSeconds(300);
            using var httpRequest = new HttpRequestMessage(HttpMethod.Post, apiUrl);
            httpRequest.Content = new StringContent(requestBody.ToJsonString(AppJsonSerializerContext.Web.Options), Encoding.UTF8, "application/json");
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
            using var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, httpContext.RequestAborted);
            response.EnsureSuccessStatusCode();
            httpContext.Response.StatusCode = (int)response.StatusCode;
            httpContext.Response.ContentType = response.Content.Headers.ContentType!.MediaType;
            using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
            using var outputStream = httpContext.Response.Body;
            await responseStream.CopyToAsync(outputStream, 128, httpContext.RequestAborted);
            await httpContext.Response.CompleteAsync();
        }
        catch (OperationCanceledException) { return; }
        catch (Exception ex) { Log.Error(ex, "聊天接口发生异常"); }
    }


    const int DimensionSize = 512; // 向量维度
    const int ChunkSize = DimensionSize * 3 / 2; // 768字符, 根据维度大小调整块大小，这里设置为维度的1.5倍左右
    const string TextEmbedURL = "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding";
    const string EmbeddingURL = "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings";
    #region Chunks Model
    public class ChunkRequest
    {
        [JsonPropertyName("content")]
        public required string Content { get; set; }
        [JsonPropertyName("path")]
        public required string Path { get; set; }
        [JsonPropertyName("embed")]
        public bool Embed { get; set; }
    }
    public class Chunk
    {
        [JsonPropertyName("hash")]
        public required string Hash { get; set; }
        [JsonPropertyName("text")]
        public required string Text { get; set; }
        [JsonPropertyName("range")]
        public required RangeNode Range { get; set; }
        [JsonPropertyName("embedding")]
        public required ChunkEmbeddingNode Embedding { get; set; }
        public class RangeNode
        {
            [JsonPropertyName("start")]
            public int Start { get; set; }
            [JsonPropertyName("end")]
            public int End { get; set; }
        }
        public class ChunkEmbeddingNode
        {
            [JsonPropertyName("embedding")]
            public required float[] Embedding { get; set; }
        }
    }
    public class ChunkResponse
    {
        [JsonPropertyName("chunks")]
        public required List<Chunk> Chunks { get; set; }
        [JsonPropertyName("embedding_model")]
        public required string EmbeddingModel { get; set; }
    }
    public class EmbeddingResponse
    {
        [JsonPropertyName("output")]
        public OutputNode Output { get; set; } = new();
        public class OutputNode
        {
            [JsonPropertyName("embeddings")]
            public EmbeddingNode[] Embeddings { get; set; } = Array.Empty<EmbeddingNode>();
        }
        public class EmbeddingNode
        {
            [JsonPropertyName("embedding")]
            public float[] Embedding { get; set; } = Array.Empty<float>();
        }
    }
    #endregion
    private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 10);
    public static async Task HandleChunks(HttpContext httpContext, [FromServices] IHttpClientFactory httpClientFactory)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }
        try
        {
            while (await Semaphore.WaitAsync(100, httpContext.RequestAborted) == false)
                await Task.Delay(100, httpContext.RequestAborted);

            ChunkRequest? request = null;
            try { request = await httpContext.Request.ReadFromJsonAsync<ChunkRequest>(httpContext.RequestAborted); } catch { return; }

            var chunks = new List<Chunk>();
            var lines = request!.Content.Split('\n');
            var currentChunk = new StringBuilder();
            int start = 0;
            static Chunk CreateChunk(string text, string path, int start, int end)
            {
                using var sha256 = SHA256.Create();
                var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
                var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
                return new Chunk
                {
                    Hash = hash,
                    Text = $"File: `{path}`\n```shell\n{text}```",
                    Range = new() { Start = start, End = end },
                    Embedding = new() { Embedding = Array.Empty<float>() }
                };
            }
            using (var reader = new StringReader(request!.Content))
            {
                string? line;
                while ((line = reader.ReadLine()) != null)
                {
                    void AppendPreHandledChunk()
                    {
                        if (currentChunk.Length > 0)
                        {
                            var chunk = CreateChunk(currentChunk.ToString(), request.Path, start, start + currentChunk.Length);
                            chunks.Add(chunk);
                            start += currentChunk.Length;
                            currentChunk.Clear();
                        }
                    }
                    if (line.Length > ChunkSize)
                    {
                        AppendPreHandledChunk();
                        int lineStart = 0;
                        while (lineStart < line.Length)
                        {
                            int length = Math.Min(ChunkSize, line.Length - lineStart);
                            var chunkText = line.Substring(lineStart, length);
                            var chunk = CreateChunk(chunkText, request.Path, start, start + chunkText.Length);
                            chunks.Add(chunk);
                            start += chunkText.Length;
                            lineStart += length;
                        }
                    }
                    else
                    {
                        if (currentChunk.Length + line.Length + 1 > ChunkSize)
                            AppendPreHandledChunk();
                        currentChunk.AppendLine(line);
                    }
                }
            }
            if (currentChunk.Length > 0)
            {
                var chunk = CreateChunk(currentChunk.ToString(), request.Path, start, start + currentChunk.Length);
                chunks.Add(chunk);
            }

            if (request.Embed)
            {
                var apiKey = Environment.GetEnvironmentVariable("ALIBAILIAN_API_KEY");
                if (string.IsNullOrEmpty(apiKey)) return;
                using var httpClient = httpClientFactory.CreateClient();
                foreach (var batchChunk in chunks.Chunk(20))
                {
                    if (httpContext.RequestAborted.IsCancellationRequested) return;

                    var texts = batchChunk.Select(chunk =>
                    {
                        var text = chunk.Text.AsSpan();
                        var idx = text.IndexOf('\n');
                        if (idx != -1) text = text[(idx + 1)..];
                        if (text.StartsWith("```shell\n")) text = text[9..];
                        if (text.EndsWith("```")) text = text[..^3];
                        return text.ToString();
                    }).ToArray();

                    var reqJson = $$"""
                    {
                        "model":"text-embedding-v2",
                        "input":{"texts":{{texts.SerializeToJsonString()}}},
                        "parameters":{"dimension":{{DimensionSize}},"text_type":"document","output_type":"dense"}
                    }
                    """;
                    using var httpRequest = new HttpRequestMessage(HttpMethod.Post, TextEmbedURL);
                    httpRequest.Content = new StringContent(reqJson, Encoding.UTF8, "application/json");
                    httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
                    using var response = await httpClient.SendAsync(httpRequest, httpContext.RequestAborted);
                    response.EnsureSuccessStatusCode();
                    using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
                    var responseObject = await httpContext.DeserializeStreamAsync<EmbeddingResponse>(responseStream);
                    if (responseObject is { Output.Embeddings.Length: > 0 } && responseObject.Output.Embeddings.Length == batchChunk.Count())
                    {
                        var embeddings = responseObject.Output.Embeddings;
                        foreach (var (index, chunk) in batchChunk.Index())
                            chunk.Embedding.Embedding = embeddings[index].Embedding;
                    }
                }
            }
            await httpContext.Response.WriteAsJsonAsync(new ChunkResponse
            {
                Chunks = chunks,
                EmbeddingModel = "text-embedding-3-small-512"
            }, AppJsonSerializerContext.Default.Options);
        }
        finally
        {
            Semaphore.Release();
        }
    }
    public static async Task HandleEmbeddings(HttpContext httpContext, [FromServices] IHttpClientFactory httpClientFactory)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        JsonNode? parseNode = null;
        try { parseNode = await JsonObject.ParseAsync(httpContext.Request.Body, null, default, httpContext.RequestAborted); }
        catch
        {
            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            return;
        }

        var updatedBody = parseNode!.AsObject();
        updatedBody["model"] = "text-embedding-v3";
        if (!updatedBody.TryGetPropertyValue("dimensions", out var dimensionsNode))
            updatedBody["dimensions"] = DimensionSize;

        var apiKey = Environment.GetEnvironmentVariable("ALIBAILIAN_API_KEY");
        try
        {
            using var httpClient = httpClientFactory.CreateClient();
            using var httpRequest = new HttpRequestMessage(HttpMethod.Post, EmbeddingURL);
            httpRequest.Content = new StringContent(updatedBody.ToJsonString(AppJsonSerializerContext.Web.Options), Encoding.UTF8, "application/json");
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
            using var response = await httpClient.SendAsync(httpRequest, httpContext.RequestAborted);
            response.EnsureSuccessStatusCode();
            using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
            using var outputStream = httpContext.Response.Body;
            await responseStream.CopyToAsync(outputStream, 512, httpContext.RequestAborted);
        }
        catch { httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; }
    }
}
